Skip to content

BUGFIX: template-require-valid-alt-text — reject empty-string aria-label/labelledby/alt on <input type=image>, <object>, <area>#15

Closed
johanrd wants to merge 3 commits intomasterfrom
fix/alt-text-empty-aria-label
Closed

BUGFIX: template-require-valid-alt-text — reject empty-string aria-label/labelledby/alt on <input type=image>, <object>, <area>#15
johanrd wants to merge 3 commits intomasterfrom
fix/alt-text-empty-aria-label

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented Apr 21, 2026

  • Premise: An empty-string aria-label="" / aria-labelledby="" / alt="" / title="" contributes no accessible name (per accname + HTML-AAM — the linked §mapping_additional_nd_name covers aria-label explicitly, and aria-labelledby / alt / title inherit via HTML-AAM). For <input type="image">, <object>, and <area>, an accessible name is required.
  • Problem: Our rule checked only for the presence of any fallback attribute, not its value — so <input type="image" aria-label="" /> was accepted.

Fix: add hasNonEmptyTextAttr() that requires a static value to be non-whitespace. Dynamic values (mustache, concat) stay accepted — we can't know at lint time whether they resolve to empty.

<img>'s alt="" is unchanged — an empty alt on <img> is spec-defined as a marker for decorative images.

Nine new invalid tests (3 elements × 3 fallback attrs) cover the fix.

Prior art

Plugin Rule Notes
jsx-a11y alt-text object / area / input[type=image] handlers treat empty aria-label as missing via ariaLabelHasValue. Empty-label validation in the img handler uses inline string messages (no ariaLabelValueError / ariaLabelledbyValueError messageIds).
vuejs-accessibility alt-text Same — treats empty aria-label/labelledby as missing.
lit-a11y alt-text Existence-only via !elementHasAttribute(element, 'alt'); does NOT validate that aria-label / aria-labelledby are non-empty.

Our fix reuses the existing error messages (inputImage, objectMissing, areaMissing); we don't split into a separate empty-label error. That's an improvement we could make later.

Upstream ember-template-lint@7.9.3 has the same false negative.

…aria-labelledby/alt

Before: for <input type="image">, <object>, and <area>, the rule checked
only for the PRESENCE of an accessible-name fallback attribute
(aria-label / aria-labelledby / alt / title). An empty-string value
provides no accessible name but slipped past.

Fix: add hasNonEmptyTextAttr() that requires the attribute's static
value to be non-whitespace. Dynamic values (mustache, concat) remain
accepted — we can't tell at lint time whether they resolve to empty.

<img>'s alt handling is unchanged — alt="" is still valid there
(spec-defined marker for decorative images).

Nine new invalid tests cover the three elements × three fallback attrs.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
js small 13.99 ms 14.13 ms +1.0%
js medium 7.10 ms 7.07 ms -0.5%
🟢 js large 2.83 ms 2.76 ms -2.6%
gjs small 1.22 ms 1.21 ms -0.7%
gjs medium 610.60 µs 607.45 µs -0.5%
gjs large 243.03 µs 242.49 µs -0.2%
gts small 1.23 ms 1.21 ms -1.6%
gts medium 616.59 µs 610.66 µs -1.0%
gts large 243.45 µs 241.79 µs -0.7%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~2.97 GHz
cpu: AMD EPYC 7763 64-Core Processor
runtime: node 24.14.1 (x64-linux)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
js small (control)            16.83 ms/iter  17.70 ms █                    
                      (12.41 ms … 29.54 ms)  28.04 ms ██                   
                    (  5.55 mb …  10.62 mb)   7.25 mb ██▆▆▆▁█▆▃▁▁▁▁▁▃▃▁▆▃▃▃

js small (experiment)         14.77 ms/iter  15.79 ms   ▅ █                
                      (12.86 ms … 20.61 ms)  18.88 ms   █ █▃     ▃         
                    (  6.16 mb …   7.87 mb)   6.83 mb ▄██▄██▁▆▄▄██▄▆▁▁▁▁▁▁▄

                             ┌                                            ┐
                             ╷┌───────────┬─┐                             ╷
          js small (control) ├┤           │ ├─────────────────────────────┤
                             ╵└───────────┴─┘                             ╵
                              ╷ ┌───┬──┐        ╷
       js small (experiment)  ├─┤   │  ├────────┤
                              ╵ └───┴──┘        ╵
                             └                                            ┘
                             12.41 ms           20.22 ms           28.04 ms

summary
  js small (experiment)
   1.14x faster than js small (control)

------------------------------------------- -------------------------------
js medium (control)            7.73 ms/iter   7.98 ms  █                   
                       (6.68 ms … 13.86 ms)  13.00 ms ██                   
                    (  3.04 mb …   4.10 mb)   3.53 mb ███▅▃▅▃▂▂▃▂▁▃▃▂▂▁▁▁▁▂

js medium (experiment)         7.53 ms/iter   8.03 ms  █                   
                       (6.54 ms … 13.23 ms)  13.02 ms ▆█▅                  
                    (  2.81 mb …   4.19 mb)   3.54 mb ███▅▃▇▇▄▁▂▂▂▂▁▁▁▁▁▁▁▂

                             ┌                                            ┐
                              ╷┌─────┬─┐                                  ╷
         js medium (control)  ├┤     │ ├──────────────────────────────────┤
                              ╵└─────┴─┘                                  ╵
                             ╷ ┌────┬──┐                                  ╷
      js medium (experiment) ├─┤    │  ├──────────────────────────────────┤
                             ╵ └────┴──┘                                  ╵
                             └                                            ┘
                             6.54 ms            9.78 ms            13.02 ms

summary
  js medium (experiment)
   1.03x faster than js medium (control)

------------------------------------------- -------------------------------
js large (control)             3.26 ms/iter   3.14 ms   █                  
                       (2.48 ms … 11.76 ms)   6.30 ms   █                  
                    (195.02 kb …   2.79 mb)   1.44 mb ▇██▃▂▃▂▁▃▂▁▁▁▂▁▁▁▁▁▂▁

js large (experiment)          3.00 ms/iter   2.84 ms  █                   
                        (2.57 ms … 7.96 ms)   5.68 ms  █                   
                    (322.11 kb …   2.56 mb)   1.43 mb ██▆▂▂▂▂▁▁▂▁▂▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷  ┌─────┬                                   ╷
          js large (control) ├──┤     │───────────────────────────────────┤
                             ╵  └─────┴                                   ╵
                              ╷┌───┬                               ╷
       js large (experiment)  ├┤   │───────────────────────────────┤
                              ╵└───┴                               ╵
                             └                                            ┘
                             2.48 ms            4.39 ms             6.30 ms

summary
  js large (experiment)
   1.08x faster than js large (control)

------------------------------------------- -------------------------------
gjs small (control)            1.35 ms/iter   1.31 ms █                    
                        (1.18 ms … 6.46 ms)   5.21 ms █                    
                    (212.49 kb …   1.86 mb)   1.06 mb ██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)         1.33 ms/iter   1.24 ms █                    
                        (1.18 ms … 6.25 ms)   5.24 ms █                    
                    (242.12 kb …   1.79 mb)   1.06 mb █▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
         gjs small (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌─┬                                          ╷
      gjs small (experiment) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             └                                            ┘
                             1.18 ms            3.21 ms             5.24 ms

summary
  gjs small (experiment)
   1.01x faster than gjs small (control)

------------------------------------------- -------------------------------
gjs medium (control)         665.76 µs/iter 625.39 µs █                    
                      (582.73 µs … 6.15 ms)   3.36 ms █                    
                    (300.74 kb …   1.10 mb) 542.47 kb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      652.72 µs/iter 621.34 µs  █                   
                      (580.32 µs … 5.67 ms)   1.43 ms ▄█                   
                    (146.08 kb …   1.28 mb) 540.85 kb ██▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
        gjs medium (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌┬            ╷
     gjs medium (experiment) ││────────────┤
                             └┴            ╵
                             └                                            ┘
                             580.32 µs           1.97 ms            3.36 ms

summary
  gjs medium (experiment)
   1.02x faster than gjs medium (control)

------------------------------------------- -------------------------------
gjs large (control)          266.26 µs/iter 258.93 µs  █                   
                      (233.53 µs … 5.34 ms) 340.81 µs ▂██                  
                    (183.11 kb … 662.31 kb) 217.41 kb ███▆▃█▆▅▂▂▂▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       264.11 µs/iter 259.42 µs  █                   
                      (234.29 µs … 4.52 ms) 332.81 µs  ██                  
                    (181.04 kb …   1.33 mb) 216.89 kb ▇██▃▂▆▇▄▂▁▂▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷ ┌───────────┬                              ╷
         gjs large (control) ├─┤           │──────────────────────────────┤
                             ╵ └───────────┴                              ╵
                             ╷ ┌──────────┬                            ╷
      gjs large (experiment) ├─┤          │────────────────────────────┤
                             ╵ └──────────┴                            ╵
                             └                                            ┘
                             233.53 µs         287.17 µs          340.81 µs

summary
  gjs large (experiment)
   1.01x faster than gjs large (control)

------------------------------------------- -------------------------------
gts small (control)            1.36 ms/iter   1.26 ms █                    
                        (1.20 ms … 6.70 ms)   5.38 ms █                    
                    (198.02 kb …   1.60 mb)   1.06 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)         1.41 ms/iter   1.24 ms █                    
                        (1.18 ms … 6.56 ms)   5.43 ms █                    
                    (220.16 kb …   1.71 mb)   1.05 mb █▂▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                         ╷
         gts small (control) │ │─────────────────────────────────────────┤
                             └─┴                                         ╵
                             ┌─┬                                          ╷
      gts small (experiment) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             └                                            ┘
                             1.18 ms            3.31 ms             5.43 ms

summary
  gts small (control)
   1.04x faster than gts small (experiment)

------------------------------------------- -------------------------------
gts medium (control)         677.29 µs/iter 628.94 µs  █                   
                      (584.77 µs … 5.30 ms)   1.45 ms ▄█                   
                    (397.97 kb …   1.29 mb) 542.35 kb ██▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      655.20 µs/iter 626.89 µs █                    
                      (582.06 µs … 5.37 ms)   1.99 ms █▄                   
                    ( 25.07 kb …   1.04 mb) 539.80 kb ██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌─┬                        ╷
        gts medium (control) ├┤ │────────────────────────┤
                             ╵└─┴                        ╵
                             ╷┌┬                                          ╷
     gts medium (experiment) ├┤│──────────────────────────────────────────┤
                             ╵└┴                                          ╵
                             └                                            ┘
                             582.06 µs           1.29 ms            1.99 ms

summary
  gts medium (experiment)
   1.03x faster than gts medium (control)

------------------------------------------- -------------------------------
gts large (control)          265.86 µs/iter 259.32 µs  █                   
                      (234.73 µs … 5.35 ms) 360.46 µs ▃█▂                  
                    (170.66 kb … 953.34 kb) 217.07 kb ███▃█▅▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁

gts large (experiment)       262.48 µs/iter 257.62 µs  █                   
                      (232.62 µs … 5.04 ms) 317.36 µs  █▅▂                 
                    (137.09 kb …   1.27 mb) 216.85 kb ▅███▂▂▇▅▅▃▂▁▂▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                              ╷┌─────────┬                                ╷
         gts large (control)  ├┤         │────────────────────────────────┤
                              ╵└─────────┴                                ╵
                             ╷┌─────────┬                  ╷
      gts large (experiment) ├┤         │──────────────────┤
                             ╵└─────────┴                  ╵
                             └                                            ┘
                             232.62 µs         296.54 µs          360.46 µs

summary
  gts large (experiment)
   1.01x faster than gts large (control)

johanrd and others added 2 commits April 21, 2026 16:29
Translates 41 cases from peer-plugin rules:
  - jsx-a11y alt-text
  - vuejs-accessibility alt-text
  - lit-a11y alt-text

Fixture documents parity after this fix:
  - Empty-string aria-label/aria-labelledby on <object>, <area>, and
    <input type=image> is now flagged (reusing existing objectMissing /
    areaMissing / inputImage messageIds).

Remaining divergences (<img alt role=presentation> accepting non-empty
alt in jsx-a11y, <img aria-label> without alt) are annotated inline.
@johanrd
Copy link
Copy Markdown
Owner Author

johanrd commented Apr 21, 2026

Moved upstream to ember-cli#2730. See that PR.

@johanrd johanrd closed this Apr 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant